﻿using System;
using System.Runtime.CompilerServices;

namespace Unity.String
{
    internal static class FormatParser
    {
        public readonly ref struct ParseResult
        {
            public readonly int Index;
            public readonly ReadOnlySpan<char> FormatString;
            public readonly int LastIndex;
            public readonly int Alignment;

            public ParseResult(int index, ReadOnlySpan<char> formatString, int lastIndex, int alignment)
            {
                Index = index;
                FormatString = formatString;
                LastIndex = lastIndex;
                Alignment = alignment;
            }
        }

        internal const int ArgLengthLimit = 16;
        internal const int WidthLimit = 1000; // Note:  -WidthLimit <  ArgAlign < WidthLimit

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ParserScanResult ScanFormatString(string format, ref int index)
        {
            var length = format.Length;
            char symbol = format[index];

            index++;
            if (symbol == '}')
            {
                // skip escaped '}'
                if (index < length && format[index] == '}')
                {
                    index++;
                    return ParserScanResult.EscapedChar;
                }
                else
                {
                    StringFormatterException.ThrowFormatError();
                    return ParserScanResult.NormalChar;
                }
            }
            else if (symbol == '{')
            {
                // skip escaped '{'
                if (index < length && format[index] == '{')
                {
                    index++;
                    return ParserScanResult.EscapedChar;
                }
                else
                {
                    index--;
                    return ParserScanResult.BraceOpen;
                }
            }
            else
            {
                // symbol is the normal char OR end of text
                return ParserScanResult.NormalChar;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ParserScanResult ScanFormatString(ReadOnlySpan<char> format, ref int index)
        {
            var length = format.Length;
            char symbol = format[index];

            index++;
            if (symbol == '}')
            {
                // skip escaped '}'
                if (index < length && format[index] == '}')
                {
                    index++;
                    return ParserScanResult.EscapedChar;
                }
                else
                {
                    StringFormatterException.ThrowFormatError();
                    return ParserScanResult.NormalChar;
                }
            }
            else if (symbol == '{')
            {
                // skip escaped '{'
                if (index < length && format[index] == '{')
                {
                    index++;
                    return ParserScanResult.EscapedChar;
                }
                else
                {
                    index--;
                    return ParserScanResult.BraceOpen;
                }
            }
            else
            {
                // symbol is the normal char OR end of text
                return ParserScanResult.NormalChar;
            }
        }

        // Accept only non-unicode numbers
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool IsDigit(char symbol) => '0' <= symbol && symbol <= '9';

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ParseResult Parse(ReadOnlySpan<char> format, int index)
        {
            var length = format.Length;

            if (index < 0 || index > length)
            {
                StringFormatterException.ThrowIndexException(index, length);
            }

            index++; // Skip `{`

            //  === Index Component ===
            //   ('0'-'9')+ WS*
            char symbol = format[index];
            if (index == length || !IsDigit(symbol))
            {
                StringFormatterException.ThrowFormatError();
            }

            int paramIndex = 0;
            do
            {
                paramIndex = (paramIndex * 10) + symbol - '0';

                if (++index == length)
                    StringFormatterException.ThrowFormatError();

                symbol = format[index];
            }
            while (IsDigit(symbol) && paramIndex < ArgLengthLimit);

            if (paramIndex >= ArgLengthLimit)
            {
                StringFormatterException.ThrowFormatException();
            }

            // skip whitespace.
            while (index < length && (symbol = format[index]) == ' ')
                index++;

            //  === Alignment Component ===
            //   comma WS* minus? ('0'-'9')+ WS*

            int alignment = 0;

            if (symbol == ',')
            {
                index++;

                // skip whitespace.
                while (index < length && (symbol = format[index]) == ' ')
                    index++;

                if (index == length)
                {
                    StringFormatterException.ThrowFormatError();
                }

                var leftJustify = false;
                if (symbol == '-')
                {
                    leftJustify = true;

                    if (++index == length)
                        StringFormatterException.ThrowFormatError();

                    symbol = format[index];
                }

                if (!IsDigit(symbol))
                {
                    StringFormatterException.ThrowFormatError();
                }

                do
                {
                    alignment = (alignment * 10) + symbol - '0';

                    if (++index == length)
                        StringFormatterException.ThrowFormatError();

                    symbol = format[index];
                }
                while (IsDigit(symbol) && alignment < WidthLimit);

                if (leftJustify)
                    alignment *= -1;
            }

            // skip whitespace.
            while (index < length && (symbol = format[index]) == ' ')
                index++;

            //  === Format String Component ===

            ReadOnlySpan<char> itemFormatSpan = default;

            if (symbol == ':')
            {
                index++;
                int formatStart = index;

                while (true)
                {
                    if (index == length)
                    {
                        StringFormatterException.ThrowFormatError();
                    }
                    symbol = format[index];

                    if (symbol == '}')
                    {
                        break;
                    }
                    else if (symbol == '{')
                    {
                        StringFormatterException.ThrowFormatError();
                    }

                    index++;
                }

                // has format
                if (index > formatStart)
                {
                    itemFormatSpan = format.Slice(formatStart, index - formatStart);
                }
            }
            else if (symbol != '}')
            {
                // Unexpected character
                StringFormatterException.ThrowFormatError();
            }

            index++; // Skip `}`
            return new ParseResult(paramIndex, itemFormatSpan, index, alignment);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ParseResult Parse(string format, int index)
        {
            var length = format.Length;
            if (index < 0 || index > length)
            {
                StringFormatterException.ThrowIndexException(index, length);
            }

            index++; // Skip `{`

            //  === Index Component ===
            //   ('0'-'9')+ WS*
            char symbol = format[index];
            if (index == length || !IsDigit(symbol))
            {
                StringFormatterException.ThrowFormatError();
            }

            int paramIndex = 0;
            do
            {
                paramIndex = (paramIndex * 10) + symbol - '0';

                if (++index == length)
                    StringFormatterException.ThrowFormatError();

                symbol = format[index];
            }
            while (IsDigit(symbol) && paramIndex < ArgLengthLimit);

            if (paramIndex >= ArgLengthLimit)
            {
                StringFormatterException.ThrowFormatException();
            }

            // skip whitespace.
            while (index < length && (symbol = format[index]) == ' ')
                index++;

            //  === Alignment Component ===
            //   comma WS* minus? ('0'-'9')+ WS*

            int alignment = 0;

            if (symbol == ',')
            {
                index++;

                // skip whitespace.
                while (index < length && (symbol = format[index]) == ' ')
                    index++;

                if (index == length)
                {
                    StringFormatterException.ThrowFormatError();
                }

                var leftJustify = false;
                if (symbol == '-')
                {
                    leftJustify = true;

                    if (++index == length)
                        StringFormatterException.ThrowFormatError();

                    symbol = format[index];
                }

                if (!IsDigit(symbol))
                {
                    StringFormatterException.ThrowFormatError();
                }

                do
                {
                    alignment = (alignment * 10) + symbol - '0';

                    if (++index == length)
                        StringFormatterException.ThrowFormatError();

                    symbol = format[index];
                }
                while (IsDigit(symbol) && alignment < WidthLimit);

                if (leftJustify)
                    alignment *= -1;
            }

            // skip whitespace.
            while (index < length && (symbol = format[index]) == ' ')
                index++;

            //  === Format String Component ===

            ReadOnlySpan<char> itemFormatSpan = default;

            if (symbol == ':')
            {
                index++;
                int formatStart = index;

                while (true)
                {
                    if (index == length)
                    {
                        StringFormatterException.ThrowFormatError();
                    }
                    symbol = format[index];

                    if (symbol == '}')
                    {
                        break;
                    }
                    else if (symbol == '{')
                    {
                        StringFormatterException.ThrowFormatError();
                    }

                    index++;
                }

                // has format
                if (index > formatStart)
                {
                    itemFormatSpan = format.AsSpan(formatStart, index - formatStart);
                }
            }
            else if (symbol != '}')
            {
                // Unexpected character
                StringFormatterException.ThrowFormatError();
            }

            index++; // Skip `}`
            return new ParseResult(paramIndex, itemFormatSpan, index, alignment);
        }
    }

    internal enum ParserScanResult
    {
        BraceOpen,
        EscapedChar,
        NormalChar,
    }
}
